package com.five.years.common.utils;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.five.years.common.Constants;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ScanResult;

import java.math.BigDecimal;
import java.util.*;

public class RedisInstance {

    private static final Logger logger = LoggerFactory.getLogger(RedisInstance.class);
    private static final String LOCKER = "_LOCKER";

    private JedisPool pool = null;

    private RedissonClient redissonClient;

    private static Map<String, RedisInstance> instanceMap = new HashMap<>();

    private RedisInstance() {

    }

    public JedisPool getPool() {
        return pool;
    }

    public RedissonClient getRedissonClient() {
        return redissonClient;
    }

    public synchronized static RedisInstance getInstance(String host, String portStr, String password, Integer database) {
        String key = host + "_" + portStr + "_" + (database == null ? "DEFAULT" : database);
        if (instanceMap.containsKey(key)) {
            return instanceMap.get(key);
        } else {
            RedisInstance redisInstance = new RedisInstance();
            int port = 11010;
            try {
                port = Integer.parseInt(portStr);
            } catch (Exception e) {
                e.printStackTrace();
            }
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(100);
            config.setMaxIdle(20);
            config.setMaxWaitMillis(10000);
            // 在borrow一个事例时是否提前进行validate操作
            config.setTestOnBorrow(true);
            password = StringUtils.isEmpty(password) ? null : password;
            if (database == null) {
                redisInstance.pool = new JedisPool(config, host, port, 2000, password);
            } else {
                redisInstance.pool = new JedisPool(config, host, port, 2000, password, database);
            }
            instanceMap.put(key, redisInstance);

            Config redissonConfig = new Config();
            SingleServerConfig singleServerConfig = redissonConfig.useSingleServer();
            singleServerConfig.setTimeout(5 * 60 * 1000);
            singleServerConfig.setAddress("redis://" + host + ":" + port);
            if (database != null) {
                singleServerConfig.setDatabase(database);
            }
            singleServerConfig.setPassword(password);
            redisInstance.redissonClient = Redisson.create(redissonConfig);

            return redisInstance;
        }
    }

    public String get(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.get(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public String get(String key, String defaultValue) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String result = jedis.get(key);
            return result == null ? defaultValue : result;
        } finally {
            releaseJedis(jedis);
        }
    }

    public <T> T get(String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String result = jedis.get(key);
            return result != null ? JSON.parseObject(result, clazz) : null;
        } catch (Exception e) {
            return null;
        } finally {
            releaseJedis(jedis);
        }
    }


    public boolean exists(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.exists(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long getLong(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String strVal = jedis.get(key);
            if (StringUtils.isNotEmpty(strVal)) {
                return Long.parseLong(strVal);
            }
            return null;
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long incr(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.incr(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long incrBy(String key, long val) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.incrBy(key, val);
        } finally {
            releaseJedis(jedis);
        }
    }

    public String set(String key, String value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.set(key, value);
        } finally {
            releaseJedis(jedis);
        }
    }

    public String set(String key, String value, int time) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String ret = jedis.set(key, value);
            expire(key, time);
            return ret;
        } finally {
            releaseJedis(jedis);
        }
    }

    public void expire(String key, String value, int time) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.set(key, value);
            if (time < 1) {
                return;
            }
            jedis.expire(key, time);
        } finally {
            releaseJedis(jedis);
        }
    }

    public boolean expire(String key, int time) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            time = time < 0 ? 0 : time;
            Long result = jedis.expire(key, time);
            return result != null && result > 0 ? true : false;
        } finally {
            releaseJedis(jedis);
        }
    }

    public long sadd(String key, String... value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.sadd(key, value);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Set<String> smembers(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.smembers(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Boolean sismember(String key, String member) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.sismember(key, member);
        } finally {
            releaseJedis(jedis);
        }
    }

    /*
     * 返回生存时间秒数
     * */
    public long ttl(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Long ret = jedis.ttl(key);
            return ret == null ? -1 : ret;
        } finally {
            releaseJedis(jedis);
        }
    }

    public long del(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.del(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Set<String> keys(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.keys(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public void push(String key, String... values) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.rpush(key, values);
        } finally {
            releaseJedis(jedis);
        }
    }

    public void rpop(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.rpop(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public void pushObject(String key, Object obj) {
        if (obj == null) {
            return;
        }
        push(key, JsonUtil.objectToJson(obj));
    }

    public boolean pushOneByOne(String key, String... values) {
        Jedis jedis = null;
        boolean locked = false;
        try {
            jedis = getJedis();
            locked = lockKey(jedis, key);
            if (locked) {
                jedis.rpush(key, values);
            }
        } finally {
            if (locked) {
                releaseLock(jedis, key);
            }
            releaseJedis(jedis);
        }
        return locked;
    }

    public <T> T popObject(String key, Class<T> clazz) {
        return JsonUtil.jsonToPojo(pop(key), clazz);
    }

    public <T> List lrange(String key, Class<T> clazz) {
        List<String> objects = lrange(key, 0, -1);
        List<T> res = new ArrayList<T>();
        for (String obj : objects) {
            res.add(JsonUtil.jsonToPojo(obj, clazz));
        }
        return res;
    }

    public Long llen(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.llen(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public String pop(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.lpop(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public String bpop(String key, int timeout) {
        Jedis jedis = null;
        if (timeout < 0) {
            timeout = 0;
        }
        try {
            jedis = getJedis();
            List<String> list = jedis.blpop(timeout, key);
            return list != null && list.size() > 1 ? list.get(1) : null;
        } finally {
            releaseJedis(jedis);
        }
    }

    public String hGet(String key, String field) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hget(key, field);
        } finally {
            releaseJedis(jedis);
        }
    }

    public ScanResult<Map.Entry<String, String>> hScan(String key, String startNum) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hscan(key, startNum);
        } finally {
            releaseJedis(jedis);
        }
    }

    public boolean isTrueHGet(String key, String field) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return Constants.TRUE.equals(jedis.hget(key, field));
        } finally {
            releaseJedis(jedis);
        }
    }

    public <T> T hGet(String key, String field, Class<T> beanType) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String value = jedis.hget(key, field);
            if (StringUtils.isNull(value)) {
                return null;
            }
            return new ObjectMapper().readValue(value, beanType);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            releaseJedis(jedis);
        }

    }

    public <T> Map<String, T> hGetAll(String key, Class<T> beanType) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Map<String, String> map = jedis.hgetAll(key);
            if (StringUtils.isNotNull(map)) {
                return null;
            }
            Map<String, T> resultMap = new HashMap<>();
            if (beanType == String.class) {
                for (String theKey : map.keySet()) {
                    resultMap.put(theKey, (T) map.get(theKey));
                }
            } else {
                for (String theKey : map.keySet()) {
                    resultMap.put(theKey, JSON.parseObject(map.get(theKey), beanType));
                }
            }
            return resultMap;
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            releaseJedis(jedis);
        }
    }

    public Map<String, String> hGetAll(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hgetAll(key);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            releaseJedis(jedis);
        }
    }

    public <T> List<T> hGetList(String key, String field, Class<T> beanType) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String value = jedis.hget(key, field);
            if (StringUtils.isEmpty(value)) {
                return null;
            }
            return JSON.parseArray(value, beanType);
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            releaseJedis(jedis);
        }

    }

    public <T> Map<String, List<T>> hGetList(String key, Class<T> beanType) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Map<String, String> map = jedis.hgetAll(key);
            if (map == null || map.size() < 1) {
                return null;
            }
            Map<String, List<T>> resultMap = new HashMap<>();
            for (Map.Entry<String, String> entry : map.entrySet()) {
                if (StringUtils.isNotEmpty(entry.getValue())) {
                    resultMap.put(entry.getKey(), JSON.parseArray(entry.getValue(), beanType));
                }
            }
            return resultMap;
        } catch (Exception e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            releaseJedis(jedis);
        }
    }

    public Set<String> hKeys(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hkeys(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    /*
     * 覆盖值返回false,插入值返回true
     * */
    public boolean hSet(String key, String field, String value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Long result = jedis.hset(key, field, value);
            return result != null && result > 0 ? true : false;
        } finally {
            releaseJedis(jedis);
        }
    }

    public boolean hSetBean(String key, String field, Object value) {
        return hSet(key, field, JsonUtil.objectToJson(value));
    }

    /*
     * 覆盖值返回false,插入值返回true
     * */
    public boolean hSetNX(String key, String field, String value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Long result = jedis.hsetnx(key, field, value);
            return result != null && result > 0 ? true : false;
        } finally {
            releaseJedis(jedis);
        }
    }

    public boolean hDel(String key, String... field) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Long result = jedis.hdel(key, field);
            return result != null && result > 0 ? true : false;
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long hAdd(String key, String field, long value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hincrBy(key, field, value);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long hLen(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hlen(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Jedis getJedis() {
        return pool.getResource();
    }

    public void releaseJedis(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    private boolean lockKey(Jedis jedis, String key) {
        if (jedis == null || StringUtils.isEmpty(key)) {
            return false;
        }
        for (int i = 0; i < 100; i++) {
            if (jedis.set(key + LOCKER, "1", "NX", "EX", 600) == null) {
                try {
                    Thread.sleep(30L);
                } catch (InterruptedException e) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return false;
    }

    private void releaseLock(Jedis jedis, String key) {
        if (jedis == null || StringUtils.isEmpty(key)) {
            return;
        }
        jedis.del(key + LOCKER);
    }

    public Integer getInt(String key, Integer defaultVal) {
        String intStr = get(key);
        Integer ret = null;
        if (StringUtils.isNotEmpty(intStr)) {
            try {
                ret = Integer.parseInt(intStr);
            } catch (Exception e) {
            }
        }
        if (ret == null) {
            ret = defaultVal;
        }
        return ret;
    }

    public Integer getInt(String key, int min, int maxAndDefault) {
        String intStr = get(key);
        Integer ret = null;
        if (StringUtils.isNotEmpty(intStr)) {
            try {
                ret = Integer.parseInt(intStr);
            } catch (Exception e) {
            }
        }
        if (ret == null || ret > maxAndDefault) {
            ret = maxAndDefault;
        } else if (ret < min) {
            ret = min;
        }
        return ret;
    }

    public Long getLong(String key, Long defaultVal) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            String strVal = jedis.get(key);
            if (StringUtils.isNotEmpty(strVal)) {
                return Long.parseLong(strVal);
            } else {
                return defaultVal;
            }
        } finally {
            releaseJedis(jedis);
        }
    }

    public BigDecimal getBigDecimal(String key, BigDecimal defaultVal) {
        String intStr = get(key);
        BigDecimal ret = null;
        if (StringUtils.isNotEmpty(intStr)) {
            try {
                ret = new BigDecimal(intStr);
            } catch (Exception e) {
            }
        }
        if (ret == null) {
            ret = defaultVal;
        }
        return ret;
    }

    public Integer hGetInt(String key, String field, Integer defaultVal) {
        String intStr = hGet(key, field);
        if (StringUtils.isNotEmpty(intStr)) {
            try {
                return Integer.parseInt(intStr);
            } catch (Exception e) {
            }
        }
        return defaultVal;
    }

    public Long hGetLong(String key, String field, Long defaultVal) {
        String intStr = hGet(key, field);
        if (StringUtils.isNotEmpty(intStr)) {
            try {
                return Long.parseLong(intStr);
            } catch (Exception e) {
            }
        }
        return defaultVal;
    }

    public BigDecimal hGetBigDecimal(String key, String field, BigDecimal defaultVal) {
        String intStr = hGet(key, field);
        BigDecimal ret = null;
        if (StringUtils.isNotEmpty(intStr)) {
            try {
                ret = new BigDecimal(intStr);
            } catch (Exception e) {
            }
        }
        if (ret == null) {
            ret = defaultVal;
        }
        return ret;
    }

    public long rPush(String key, String... value) {

        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.rpush(key, value);
        } finally {
            releaseJedis(jedis);
        }
    }

    public List<String> lrange(String key, int start, int end) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.lrange(key, start, end);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long decr(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.decr(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long zAdd(String key, Map<String, Double> scoreMembers) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zadd(key, scoreMembers);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Integer zIncrBy(String key, Double increment, String member) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return new Double(jedis.zincrby(key, increment, member)).intValue();
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long zcard(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zcard(key);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long zAdd(String key, Double score, String member) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zadd(key, score, member);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Double zScore(String key, String member) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zscore(key, member);
        } finally {
            releaseJedis(jedis);
        }
    }


    public Long zRank(String key, String member) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zrank(key, member);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Long zremrangeByRank(String key, int start, int end) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zremrangeByRank(key, start, end);
        } finally {
            releaseJedis(jedis);
        }
    }

    public Set<String> zRange(String key, int start, int end) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.zrange(key, start, end);
        } finally {
            releaseJedis(jedis);
        }
    }

    public <T> List zRange(String key, int start, int end, Class<T> clazz) {
        Jedis jedis = null;
        List<T> res = new ArrayList<T>();
        try {
            jedis = getJedis();
            Set<String> objects = jedis.zrange(key, start, end);
            if (objects == null) {
                return null;
            }
            for (String obj : objects) {
                res.add(JsonUtil.jsonToPojo(obj, clazz));
            }
        } finally {
            releaseJedis(jedis);
        }
        return res;
    }

    public <T> List zRevRange(String key, int start, int end, Class<T> clazz) {
        Jedis jedis = null;
        List<T> res = new ArrayList<T>();
        try {
            jedis = getJedis();
            Set<String> objects = jedis.zrevrange(key, start, end);
            if (objects == null) {
                return null;
            }
            for (String obj : objects) {
                res.add(JsonUtil.jsonToPojo(obj, clazz));
            }
        } finally {
            releaseJedis(jedis);
        }
        return res;
    }
}
